package service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

import web.model.UUID;

import dao.AlbumDao;
import dao.ArtistDao;
import dao.CreditDao;
import dao.PerformDao;
import dao.TrackDao;

import message.AlbumMsg;
import message.AlbumsMsg;
import message.AlbumsShortInfoMsg;
import message.ArtistMsg;
import message.ArtistsMsg;
import message.DbMsg;
import message.GetAlbumMsg;
import message.GetAlbumsMsg;
import message.GetAlbumsShortInfoMsg;
import message.GetArtistMsg;
import message.GetArtistsMsg;
import message.RemoveAlbumMsg;
import message.StoreAlbumMsg;
import model.Album;
import model.AlbumBean;
import model.Artist;
import model.ArtistBean;
import model.Credit;
import model.CreditBean;
import model.PerformBean;
import model.ShortInformationBean;
import model.Track;
import model.TrackBean;

import framework.Trait;
import framework.server.ServerException;
import framework.service.MessageService;

public class DbService extends MessageService {

	private static final int MIN_Q_LEN = 3;
	private AlbumDao albumDao;
	private ArtistDao artistDao;
	private TrackDao trackDao;
	private CreditDao creditDao;
	private PerformDao performDao;

	public DbService(Trait container, String serviceName) {
		super(container, serviceName);
	}

	@Override
	public void init() throws ServerException {
		albumDao = new AlbumDao();
		artistDao = new ArtistDao();
		trackDao = new TrackDao();
		creditDao = new CreditDao();
		performDao = new PerformDao();
	}

	public void process(GetAlbumsShortInfoMsg request) {
		String agent = request.getAgent();
		String q = request.getQ();
		int limit = request.getLimit();
		Collection<ShortInformationBean> albums = getAlbumsShortInformation(q, limit);
		AlbumsShortInfoMsg answerMsg = new AlbumsShortInfoMsg(albums);
		sendMessage(answerMsg, getName(), agent);
	}

	// TODO dell.me
	public void process(GetAlbumsMsg request) {
		String agent = request.getAgent();
		Collection<Album> albums = getAlbums();
		AlbumsMsg answerMsg = new AlbumsMsg(albums);
		sendMessage(answerMsg, getName(), agent);
	}

	// TODO dell.me
	public void process(GetArtistsMsg request) {
		String agent = request.getAgent();
		Collection<Artist> artists = getArtists();
		ArtistsMsg answerMsg = new ArtistsMsg(artists);
		sendMessage(answerMsg, getName(), agent);
	}

	public void process(GetAlbumMsg request) {
		String agent = request.getAgent();
		String catalog = request.getCatalog();
		Album album = getAlbum(catalog);
		AlbumMsg answerMsg = new AlbumMsg(album);
		sendMessage(answerMsg, getName(), agent);
	}

	public void process(StoreAlbumMsg request) {
		String agent = request.getAgent();
		Album album = request.getAlbum();
		int result = storeAlbum(album);
		DbMsg answerMsg = new DbMsg(result);
		sendMessage(answerMsg, getName(), agent);
	}

	public void process(RemoveAlbumMsg request) {
		String agent = request.getAgent();
		String catalog = request.getCatalog();
		int result = removeAlbum(catalog);
		DbMsg answerMsg = new DbMsg(result);
		sendMessage(answerMsg, getName(), agent);
	}

	public void process(GetArtistMsg request) {
		String agent = request.getAgent();
		String name = request.getArtist();
		Artist artist = getArtist(name);
		ArtistMsg answerMsg = new ArtistMsg(artist);
		sendMessage(answerMsg, getName(), agent);
	}

	/**
	 * Get album info by a query. Searches in album catalog, album title and artist names.
	 * @param q query
	 * @param limit how many return items
	 * @return list of short info beans
	 */
	private Collection<ShortInformationBean> getAlbumsShortInformation(String q, int limit) {
		if (q.length() < MIN_Q_LEN) {
			return null;
		}
		Collection<ShortInformationBean> beans = new HashSet<ShortInformationBean>();
		Collection<Album> albums = new HashSet<Album>(); 
		albums.addAll(getAlbums(albumDao.getAlbumLikeCatalog(q), false));
		albums.addAll(getAlbums(albumDao.getAlbumLikeTitle(q), false));
		albums.addAll(getAlbums(artistDao.getArtistLikeName(q)));
//			if (album.getCatalog().toLowerCase().contains(q.toLowerCase())) {
		for (Album album: albums) {
			String description = "";
			for (Artist artist: album.getArtists()) {
				if (!description.isEmpty()) {
					description += " & ";
				}
				description += artist.getName();
			}
			ShortInformationBean bean = new ShortInformationBean(album.getCatalog(), album.getTitle(), description);
			beans.add(bean);
			if (beans.size() >= limit) {
				break;
			}
		}
		return beans;
	}

	// TODO dell.me
	private Collection<Album> getAlbums() {
		Collection<Album> albums = getAlbums(albumDao.findAll(), false);
		return albums;
	}

	// TODO dell.me
	private Collection<Artist> getArtists() {
		Collection<Artist> artists = getArtists(artistDao.findAll(), false);
		return artists;
	}

	private int storeAlbum(Album album) {
		AlbumBean albumBean = new AlbumBean(album);
		int result = albumDao.saveOrUpdate(albumBean);
		int i = 0;
		for (Artist artist: album.getArtists()) {
			ArtistBean artistBean = new ArtistBean(artist);
			result += artistDao.saveOrUpdate(artistBean);
			PerformBean performBean = new PerformBean(albumBean.getId(), artistBean.getId(), i++);
			result += performDao.saveOrUpdate(performBean);
		}
		i = 0;
		for (Track track: album.getTracks()) {
			TrackBean trackBean = new TrackBean(albumBean.getId(), i++, track);
			result += trackDao.saveOrUpdate(trackBean);
		}
		i = 0;
		for (Credit credit: album.getCredits()) {
			CreditBean creditBean = new CreditBean(albumBean.getId(), i++, credit);
			result += creditDao.saveOrUpdate(creditBean);
			int j = 0;
			for (Artist artist: credit.getArtists()) {
				ArtistBean artistBean = new ArtistBean(artist);
				result += artistDao.saveOrUpdate(artistBean);
				PerformBean performBean = new PerformBean(creditBean.getId(), artistBean.getId(), j++);
				result += performDao.saveOrUpdate(performBean);
			}
		}
		return result;
	}

	public int removeAlbum(String catalog) {
		AlbumBean album = albumDao.getAlbumByCatalog(catalog);
		int result = albumDao.remove(catalog);
		Collection<PerformBean> performs = performDao.getPerformByAlbumId(album.getId());
		for (PerformBean perform: performs) {
			result += performDao.remove(perform.getId());
			if (performDao.getPerformByArtistId(perform.getArtist()) == null) {
				/** no more performances of the artist */
				result += artistDao.remove(perform.getArtist());
			}
		}
		Collection<TrackBean> tracks = trackDao.getTrackByAlbumId(album.getId());
		for (TrackBean track: tracks) {
			result += trackDao.remove(track.getId());
		}
		Collection<CreditBean> credits = creditDao.getCreditByAlbumId(album.getId());
		for (CreditBean credit: credits) {
			result += creditDao.remove(credit.getId());
			performs = performDao.getPerformByAlbumId(credit.getId());
			for (PerformBean perform: performs) {
				result += performDao.remove(perform.getId());
				if (performDao.getPerformByArtistId(perform.getArtist()) == null) {
					/** no more performances of the artist */
					result += artistDao.remove(perform.getArtist());
				}
			}
		}
		return result;
	}

	/**
	 * Gets album by catalog
	 * @param catalog
	 * @return album
	 */
	private Album getAlbum(String catalog) {
		Album album = getAlbum(albumDao.getAlbumByCatalog(catalog), true);
		return album;
	}

	/**
	 * Gets list of albums for list of album beans
	 * @param beans album beans
	 * @param complete to include details or not
	 * @return list of albums
	 */
	private Collection<Album> getAlbums(Collection<AlbumBean> beans, boolean complete) {
		Collection<Album> albums = new ArrayList<Album>();
		for (AlbumBean bean: beans) {
			Album album = getAlbum(bean, complete);
			albums.add(album);
		}
		return albums;
	}

	/**
	 * Gets album for album bean
	 * @param bean album bean
	 * @param complete to include details or not
	 * @return album
	 */
	private Album getAlbum(AlbumBean bean, boolean complete) {
		Album album = new Album(bean.getReleased(), bean.getItem(), bean.getCatalog(), bean.getFormat(), bean.getTitle(), null, null, null);
		Collection<Artist> artists = getArtists(bean.getId());
		album.setArtists(artists);
		if (complete) {
			Collection<Track> tracks = getTracks(album, trackDao.getTrackByAlbumId(bean.getId()));
			album.setTracks(tracks);
			Collection<Credit> credits = getCredits(album, creditDao.getCreditByAlbumId(bean.getId()));
			album.setCredits(credits);
		}
		return album;
	}

	/**
	 * Gets list of albums for list of artist beans
	 * @param artists artist beans
	 * @return list of albums
	 */
	private Collection<Album> getAlbums(Collection<ArtistBean> artists) {
		Collection<Album> albums = new ArrayList<Album>();
		for (ArtistBean artist: artists) {
			albums.addAll(getAlbums(artist.getId()));
		}
		return albums;
	}

	/**
	 * Gets list of albums by artist id
	 * @param id artist id
	 * @return list of albums
	 */
	private Collection<Album> getAlbums(UUID id) {
		Collection<PerformBean> performs = performDao.getPerformByArtistId(id);
		Collection<Album> albums = new ArrayList<Album>();
		for (PerformBean perform: performs) {
			AlbumBean bean = albumDao.getAlbumById(perform.getAlbum().toString());
			if (bean != null) {
				albums.add(getAlbum(bean, false));
			}
		}
		return albums;
	}


	/**
	 * Gets artist by name
	 * @param name
	 * @return artist
	 */
	private Artist getArtist(String name) {
		Artist artist = getArtist(artistDao.getArtistByName(name), true);
		return artist;
	}

	/**
	 * Gets list of artists by album (or credit) id
	 * @param id album id
	 * @return list of artists
	 */
	private Collection<Artist> getArtists(UUID id) {
		Collection<PerformBean> performs = performDao.getPerformByAlbumId(id);
		Collection<Artist> artists = new ArrayList<Artist>();
		for (PerformBean perform: performs) {
			artists.add(getArtist(artistDao.getArtistById(perform.getArtist().toString()), false));
		}
		return artists;
	}

	/**
	 * Gets list of artists for list of artist beans
	 * @param beans artist beans
	 * @return list of artists
	 */
	private Collection<Artist> getArtists(Collection<ArtistBean> beans, boolean complete) {
		Collection<Artist> artists = new ArrayList<Artist>();
		for (ArtistBean bean: beans) {
			Artist artist = getArtist(bean, complete);
			artists.add(artist);
		}
		return artists;
	}

	/**
	 * Gets artist for artist bean
	 * @param bean artist bean
	 * @param complete to include details or not
	 * @return artist
	 */
	private Artist getArtist(ArtistBean bean, boolean complete) {
		Artist artist = new Artist(bean.getName(), null, null);
		if (complete) {
			Collection<Album> albums = getAlbums(bean.getId());
			artist.setAlbums(albums);
			Collection<Credit> credits = getCredits(bean.getId());
			artist.setCredits(credits);
		}
		return artist;
	}

	/**
	 * Gets list of credits for album and list of credit beans
	 * @param album
	 * @param beans list of credit beans
	 * @return list of credits
	 */
	private Collection<Credit> getCredits(Album album, Collection<CreditBean> beans) {
		Collection<Credit> credits = new ArrayList<Credit>();
		for (CreditBean bean: beans) {
			Credit credit = getCredit(album, bean);
			credits.add(credit);
		}
		return credits;
	}

	/**
	 * Gets credit for album and credit bean
	 * @param album
	 * @param bean credit bean
	 * @return credit
	 */
	private Credit getCredit(Album album, CreditBean bean) { // TODO ? complete
		Credit credit = new Credit(album, bean.getRole(), bean.getTracks(), null);
		Collection<Artist> artists = getArtists(bean.getId());
		credit.setArtists(artists);
		return credit;
	}

	/**
	 * Gets list of credits by artist id
	 * @param id artist id
	 * @return list of credits
	 */
	private Collection<Credit> getCredits(UUID id) {
		Collection<PerformBean> performs = performDao.getPerformByArtistId(id);
		Collection<Credit> credits = new ArrayList<Credit>();
		for (PerformBean perform: performs) {
			CreditBean bean = creditDao.getCreditById(perform.getAlbum().toString());
			if (bean != null) {
				Credit credit = getCredit(null, bean);
				Album album = getAlbum(albumDao.getAlbumById(bean.getAlbum().toString()), false);
				credit.setAlbum(album);
				credits.add(credit);
			}
		}
		return credits;
	}

	/**
	 * Gets list of tracks for album and list of track bean
	 * @param album
	 * @param beans list of track beans
	 * @return list of tracks
	 */
	private Collection<Track> getTracks(Album album, Collection<TrackBean> beans) {
		Collection<Track> tracks = new ArrayList<Track>();
		for (TrackBean bean: beans) {
			Track track = getTrack(album, bean);
			tracks.add(track);
		}
		return tracks;
	}

	/**
	 * Gets track for album and track bean
	 * @param album
	 * @param bean track bean
	 * @return track
	 */
	private Track getTrack(Album album, TrackBean bean) {
		Track track = new Track(album, bean.getPosition(), bean.getTitle(), bean.getDuration());
		return track;
	}
}
